home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2018 July / PCgo 07-2018 CD-ROM Germany.iso / nw.pak / Unnamed File 004899.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  14.4 KB  |  399 lines

  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. // chrome.runtime.messaging API implementation.
  6.  
  7.   // TODO(kalman): factor requiring chrome out of here.
  8.   var chrome = requireNative('chrome').GetChrome();
  9.   var Event = require('event_bindings').Event;
  10.   var lastError = require('lastError');
  11.   var logActivity = requireNative('activityLogger');
  12.   var logging = requireNative('logging');
  13.   var messagingNatives = requireNative('messaging_natives');
  14.   var processNatives = requireNative('process');
  15.   var unloadEvent = require('unload_event');
  16.   var utils = require('utils');
  17.   var messagingUtils = require('messaging_utils');
  18.  
  19.   // The reserved channel name for the sendRequest/send(Native)Message APIs.
  20.   // Note: sendRequest is deprecated.
  21.   var kRequestChannel = "chrome.extension.sendRequest";
  22.   var kMessageChannel = "chrome.runtime.sendMessage";
  23.   var kNativeMessageChannel = "chrome.runtime.sendNativeMessage";
  24.  
  25.   // Map of port IDs to port object.
  26.   var ports = {};
  27.  
  28.   // Map of port IDs to unloadEvent listeners. Keep track of these to free the
  29.   // unloadEvent listeners when ports are closed.
  30.   var portReleasers = {};
  31.  
  32.   // Change even to odd and vice versa, to get the other side of a given
  33.   // channel.
  34.   function getOppositePortId(portId) { return portId ^ 1; }
  35.  
  36.   // Port object.  Represents a connection to another script context through
  37.   // which messages can be passed.
  38.   function PortImpl(portId, opt_name) {
  39.     this.portId_ = portId;
  40.     this.name = opt_name;
  41.  
  42.     var portSchema = {name: 'port', $ref: 'runtime.Port'};
  43.     var options = {unmanaged: true};
  44.     this.onDisconnect = new Event(null, [portSchema], options);
  45.     this.onMessage = new Event(
  46.         null,
  47.         [{name: 'message', type: 'any', optional: true}, portSchema],
  48.         options);
  49.     this.onDestroy_ = null;
  50.   }
  51.  
  52.   // Sends a message asynchronously to the context on the other end of this
  53.   // port.
  54.   PortImpl.prototype.postMessage = function(msg) {
  55.     // JSON.stringify doesn't support a root object which is undefined.
  56.     if (msg === undefined)
  57.       msg = null;
  58.     msg = $JSON.stringify(msg);
  59.     if (msg === undefined) {
  60.       // JSON.stringify can fail with unserializable objects. Log an error and
  61.       // drop the message.
  62.       //
  63.       // TODO(kalman/mpcomplete): it would be better to do the same validation
  64.       // here that we do for runtime.sendMessage (and variants), i.e. throw an
  65.       // schema validation Error, but just maintain the old behaviour until
  66.       // there's a good reason not to (http://crbug.com/263077).
  67.       console.error('Illegal argument to Port.postMessage');
  68.       return;
  69.     }
  70.     messagingNatives.PostMessage(this.portId_, msg);
  71.   };
  72.  
  73.   // Disconnects the port from the other end.
  74.   PortImpl.prototype.disconnect = function() {
  75.     messagingNatives.CloseChannel(this.portId_, true);
  76.     this.destroy_();
  77.   };
  78.  
  79.   PortImpl.prototype.destroy_ = function() {
  80.     var portId = this.portId_;
  81.  
  82.     if (this.onDestroy_)
  83.       this.onDestroy_();
  84.     privates(this.onDisconnect).impl.destroy_();
  85.     privates(this.onMessage).impl.destroy_();
  86.  
  87.     messagingNatives.PortRelease(portId);
  88.     unloadEvent.removeListener(portReleasers[portId]);
  89.  
  90.     delete ports[portId];
  91.     delete portReleasers[portId];
  92.   };
  93.  
  94.   // Returns true if the specified port id is in this context. This is used by
  95.   // the C++ to avoid creating the javascript message for all the contexts that
  96.   // don't care about a particular message.
  97.   function hasPort(portId) {
  98.     return portId in ports;
  99.   };
  100.  
  101.   // Hidden port creation function.  We don't want to expose an API that lets
  102.   // people add arbitrary port IDs to the port list.
  103.   function createPort(portId, opt_name) {
  104.     if (ports[portId])
  105.       throw new Error("Port '" + portId + "' already exists.");
  106.     var port = new Port(portId, opt_name);
  107.     ports[portId] = port;
  108.     portReleasers[portId] = $Function.bind(messagingNatives.PortRelease,
  109.                                            this,
  110.                                            portId);
  111.     unloadEvent.addListener(portReleasers[portId]);
  112.     messagingNatives.PortAddRef(portId);
  113.     return port;
  114.   };
  115.  
  116.   // Helper function for dispatchOnRequest.
  117.   function handleSendRequestError(isSendMessage,
  118.                                   responseCallbackPreserved,
  119.                                   sourceExtensionId,
  120.                                   targetExtensionId,
  121.                                   sourceUrl) {
  122.     var errorMsg = [];
  123.     var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
  124.     if (isSendMessage && !responseCallbackPreserved) {
  125.       $Array.push(errorMsg,
  126.           "The chrome." + eventName + " listener must return true if you " +
  127.           "want to send a response after the listener returns");
  128.     } else {
  129.       $Array.push(errorMsg,
  130.           "Cannot send a response more than once per chrome." + eventName +
  131.           " listener per document");
  132.     }
  133.     $Array.push(errorMsg, "(message was sent by extension" + sourceExtensionId);
  134.     if (sourceExtensionId != "" && sourceExtensionId != targetExtensionId)
  135.       $Array.push(errorMsg, "for extension " + targetExtensionId);
  136.     if (sourceUrl != "")
  137.       $Array.push(errorMsg, "for URL " + sourceUrl);
  138.     lastError.set(eventName, errorMsg.join(" ") + ").", null, chrome);
  139.   }
  140.  
  141.   // Helper function for dispatchOnConnect
  142.   function dispatchOnRequest(portId, channelName, sender,
  143.                              sourceExtensionId, targetExtensionId, sourceUrl,
  144.                              isExternal) {
  145.     var isSendMessage = channelName == kMessageChannel;
  146.     var requestEvent = null;
  147.     if (isSendMessage) {
  148.       if (chrome.runtime) {
  149.         requestEvent = isExternal ? chrome.runtime.onMessageExternal
  150.                                   : chrome.runtime.onMessage;
  151.       }
  152.     } else {
  153.       if (chrome.extension) {
  154.         requestEvent = isExternal ? chrome.extension.onRequestExternal
  155.                                   : chrome.extension.onRequest;
  156.       }
  157.     }
  158.     if (!requestEvent)
  159.       return false;
  160.     if (!requestEvent.hasListeners())
  161.       return false;
  162.     var port = createPort(portId, channelName);
  163.  
  164.     function messageListener(request) {
  165.       var responseCallbackPreserved = false;
  166.       var responseCallback = function(response) {
  167.         if (port) {
  168.           port.postMessage(response);
  169.           privates(port).impl.destroy_();
  170.           port = null;
  171.         } else {
  172.           // We nulled out port when sending the response, and now the page
  173.           // is trying to send another response for the same request.
  174.           handleSendRequestError(isSendMessage, responseCallbackPreserved,
  175.                                  sourceExtensionId, targetExtensionId);
  176.         }
  177.       };
  178.       // In case the extension never invokes the responseCallback, and also
  179.       // doesn't keep a reference to it, we need to clean up the port. Do
  180.       // so by attaching to the garbage collection of the responseCallback
  181.       // using some native hackery.
  182.       messagingNatives.BindToGC(responseCallback, function() {
  183.         if (port) {
  184.           privates(port).impl.destroy_();
  185.           port = null;
  186.         }
  187.       });
  188.       var rv = requestEvent.dispatch(request, sender, responseCallback);
  189.       if (isSendMessage) {
  190.         responseCallbackPreserved =
  191.             rv && rv.results && $Array.indexOf(rv.results, true) > -1;
  192.         if (!responseCallbackPreserved && port) {
  193.           // If they didn't access the response callback, they're not
  194.           // going to send a response, so clean up the port immediately.
  195.           privates(port).impl.destroy_();
  196.           port = null;
  197.         }
  198.       }
  199.     }
  200.  
  201.     privates(port).impl.onDestroy_ = function() {
  202.       port.onMessage.removeListener(messageListener);
  203.     };
  204.     port.onMessage.addListener(messageListener);
  205.  
  206.     var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
  207.     if (isExternal)
  208.       eventName += "External";
  209.     logActivity.LogEvent(targetExtensionId,
  210.                          eventName,
  211.                          [sourceExtensionId, sourceUrl]);
  212.     return true;
  213.   }
  214.  
  215.   // Called by native code when a channel has been opened to this context.
  216.   function dispatchOnConnect(portId,
  217.                              channelName,
  218.                              sourceTab,
  219.                              sourceFrameId,
  220.                              guestProcessId,
  221.                              sourceExtensionId,
  222.                              targetExtensionId,
  223.                              sourceUrl,
  224.                              tlsChannelId) {
  225.     // Only create a new Port if someone is actually listening for a connection.
  226.     // In addition to being an optimization, this also fixes a bug where if 2
  227.     // channels were opened to and from the same process, closing one would
  228.     // close both.
  229.     var extensionId = processNatives.GetExtensionId();
  230.  
  231.     // messaging_bindings.cc should ensure that this method only gets called for
  232.     // the right extension.
  233.     logging.CHECK(targetExtensionId == extensionId);
  234.  
  235.     if (ports[getOppositePortId(portId)])
  236.       return false;  // this channel was opened by us, so ignore it
  237.  
  238.     // Determine whether this is coming from another extension, so we can use
  239.     // the right event.
  240.     var isExternal = sourceExtensionId != extensionId;
  241.  
  242.     var sender = {};
  243.     if (sourceExtensionId != '')
  244.       sender.id = sourceExtensionId;
  245.     if (sourceUrl)
  246.       sender.url = sourceUrl;
  247.     if (sourceTab)
  248.       sender.tab = sourceTab;
  249.     if (sourceFrameId >= 0)
  250.       sender.frameId = sourceFrameId;
  251.     if (typeof guestProcessId != 'undefined') {
  252.       // Note that |guestProcessId| is not a standard field on MessageSender and
  253.       // should not be exposed to drive-by extensions; it is only exposed to
  254.       // component extensions.
  255.       logging.CHECK(processNatives.IsComponentExtension(),
  256.           "GuestProcessId can only be exposed to component extensions.");
  257.       sender.guestProcessId = guestProcessId;
  258.     }
  259.     if (typeof tlsChannelId != 'undefined')
  260.       sender.tlsChannelId = tlsChannelId;
  261.  
  262.     // Special case for sendRequest/onRequest and sendMessage/onMessage.
  263.     if (channelName == kRequestChannel || channelName == kMessageChannel) {
  264.       return dispatchOnRequest(portId, channelName, sender,
  265.                                sourceExtensionId, targetExtensionId, sourceUrl,
  266.                                isExternal);
  267.     }
  268.  
  269.     var connectEvent = null;
  270.     if (chrome.runtime) {
  271.       connectEvent = isExternal ? chrome.runtime.onConnectExternal
  272.                                 : chrome.runtime.onConnect;
  273.     }
  274.     if (!connectEvent)
  275.       return false;
  276.     if (!connectEvent.hasListeners())
  277.       return false;
  278.  
  279.     var port = createPort(portId, channelName);
  280.     port.sender = sender;
  281.     if (processNatives.manifestVersion < 2)
  282.       port.tab = port.sender.tab;
  283.  
  284.     var eventName = (isExternal ?
  285.         "runtime.onConnectExternal" : "runtime.onConnect");
  286.     connectEvent.dispatch(port);
  287.     logActivity.LogEvent(targetExtensionId,
  288.                          eventName,
  289.                          [sourceExtensionId]);
  290.     return true;
  291.   };
  292.  
  293.   // Called by native code when a channel has been closed.
  294.   function dispatchOnDisconnect(portId, errorMessage) {
  295.     var port = ports[portId];
  296.     if (port) {
  297.       // Update the renderer's port bookkeeping, without notifying the browser.
  298.       messagingNatives.CloseChannel(portId, false);
  299.       if (errorMessage)
  300.         lastError.set('Port', errorMessage, null, chrome);
  301.       try {
  302.         port.onDisconnect.dispatch(port);
  303.       } finally {
  304.         privates(port).impl.destroy_();
  305.         lastError.clear(chrome);
  306.       }
  307.     }
  308.   };
  309.  
  310.   // Called by native code when a message has been sent to the given port.
  311.   function dispatchOnMessage(msg, portId) {
  312.     var port = ports[portId];
  313.     if (port) {
  314.       if (msg)
  315.         msg = $JSON.parse(msg);
  316.       port.onMessage.dispatch(msg, port);
  317.     }
  318.   };
  319.  
  320.   // Shared implementation used by tabs.sendMessage and runtime.sendMessage.
  321.   function sendMessageImpl(port, request, responseCallback) {
  322.     if (port.name != kNativeMessageChannel)
  323.       port.postMessage(request);
  324.  
  325.     if (port.name == kMessageChannel && !responseCallback) {
  326.       // TODO(mpcomplete): Do this for the old sendRequest API too, after
  327.       // verifying it doesn't break anything.
  328.       // Go ahead and disconnect immediately if the sender is not expecting
  329.       // a response.
  330.       port.disconnect();
  331.       return;
  332.     }
  333.  
  334.     // Ensure the callback exists for the older sendRequest API.
  335.     if (!responseCallback)
  336.       responseCallback = function() {};
  337.  
  338.     // Note: make sure to manually remove the onMessage/onDisconnect listeners
  339.     // that we added before destroying the Port, a workaround to a bug in Port
  340.     // where any onMessage/onDisconnect listeners added but not removed will
  341.     // be leaked when the Port is destroyed.
  342.     // http://crbug.com/320723 tracks a sustainable fix.
  343.  
  344.     function disconnectListener() {
  345.       // For onDisconnects, we only notify the callback if there was an error.
  346.       if (chrome.runtime && chrome.runtime.lastError)
  347.         responseCallback();
  348.     }
  349.  
  350.     function messageListener(response) {
  351.       try {
  352.         responseCallback(response);
  353.       } finally {
  354.         port.disconnect();
  355.       }
  356.     }
  357.  
  358.     privates(port).impl.onDestroy_ = function() {
  359.       port.onDisconnect.removeListener(disconnectListener);
  360.       port.onMessage.removeListener(messageListener);
  361.     };
  362.     port.onDisconnect.addListener(disconnectListener);
  363.     port.onMessage.addListener(messageListener);
  364.   };
  365.  
  366.   function sendMessageUpdateArguments(functionName, hasOptionsArgument) {
  367.     // skip functionName and hasOptionsArgument
  368.     var args = $Array.slice(arguments, 2);
  369.     var alignedArgs = messagingUtils.alignSendMessageArguments(args,
  370.         hasOptionsArgument);
  371.     if (!alignedArgs)
  372.       throw new Error('Invalid arguments to ' + functionName + '.');
  373.     return alignedArgs;
  374.   }
  375.  
  376. var Port = utils.expose('Port', PortImpl, { functions: [
  377.     'disconnect',
  378.     'postMessage'
  379.   ],
  380.   properties: [
  381.     'name',
  382.     'onDisconnect',
  383.     'onMessage'
  384.   ] });
  385.  
  386. exports.kRequestChannel = kRequestChannel;
  387. exports.kMessageChannel = kMessageChannel;
  388. exports.kNativeMessageChannel = kNativeMessageChannel;
  389. exports.Port = Port;
  390. exports.createPort = createPort;
  391. exports.sendMessageImpl = sendMessageImpl;
  392. exports.sendMessageUpdateArguments = sendMessageUpdateArguments;
  393.  
  394. // For C++ code to call.
  395. exports.hasPort = hasPort;
  396. exports.dispatchOnConnect = dispatchOnConnect;
  397. exports.dispatchOnDisconnect = dispatchOnDisconnect;
  398. exports.dispatchOnMessage = dispatchOnMessage;
  399.